package com.itheima.consumer.mq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.time.LocalTime;
import java.util.Map;

// 消费者
@Slf4j
@Component
public class SpringRabbitListener {

    /**
     * 测试消费者确认机制，模拟消费消息时发生异常。
     * 1.none：表示不进行确认，无论消费者业务逻辑处理是否抛出异常，都会直接 ack；
     * 2.auto：表示自动确认，并根据异常类型判断进行消息重消费或者拒绝消费（消息转换异常会导致拒绝消费）。
     * 3.manual：手动确认，需要手写确认逻辑，会有代码侵入。
     */
    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(Message message) {
        log.info("监听到simple.queue的消息：ID：【{}】", message.getMessageProperties().getMessageId());
        log.info("监听到simple.queue的消息：【{}】", new String(message.getBody()));
        throw new RuntimeException("模拟消费消息时发生运行时异常，我是故意的~");
    }

//    @RabbitListener(queues = "simple.queue")
//    public void listenSimpleQueue(String message) {
//        log.info("监听到simple.queue的消息：【{}】", message);
//        throw new MessageConversionException("模拟消费消息时发生消息转换异常，我是故意的~");
//    }

//    @RabbitListener(queues = "simple.queue")
//    public void listenSimpleQueue(String message) {
//        log.info("监听到simple.queue的消息：【{}】", message);
//        throw new RuntimeException("模拟消费消息时发生运行时异常，我是故意的~");
//    }

//    @RabbitListener(queues = "simple.queue")
//    public void listenSimpleQueue(String message) {
//        log.info("监听到simple.queue的消息：【{}】", message);
//    }

    /**
     * 根据测试得出结论：
     *    1.无论两个消费者的消息处理速度是否相同或者处理速度相差较大，两个消费者都是平均分配消息进行处理的，轮询进行处理。
     *    2.可以通过配置参数 prefetch: 个数 来控制每个消费者能同时处理的消息数量。
     *    Spring:
     *      rabbitmq:
     *        listener:
     *           simple:
     *              prefetch: 1  #每次只能获取一条消息，处理完才能获取下一个消息，这样处理速度较慢的消费者必须处理完后才能获取下一个消息，处理速度快的消费者获得的消息会多，不会再平均的分配消息了。
     *
     * @param message
     */
    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue1(String message) {
        System.out.println("消费者11111接收到消息：" + message + ", " + LocalTime.now());
        // 模拟该服务器性能稍低的情况，看看是不是两个消费者还是平均分配消息。
        try {
            Thread.sleep(25); // 每条处理时间为25毫秒，每秒大概处理40条消息。listenWorkQueue1和listenWorkQueue2大概在1秒多点的时间里处理完50条消息。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue2(String message) {
        System.out.println("消费者22222接收到消息：" + message + ", " + LocalTime.now());
        // 模拟该服务器性能稍低的情况，看看是不是两个消费者还是平均分配消息。
        try {
            Thread.sleep(200); // 每条处理时间为200毫秒，每秒大概处理5条消息。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * (1) 使用Fanout交换机发送消息测试
     * 广播模式：每个队列只要绑定上交换机就会收到消息。
     * @param message
     */
    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue(String message) {
        log.info("消费者1监听到 fanout.queue1 的消息：【{}】", message);
    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String message) {
        log.info("消费者2监听到 fanout.queue2 的消息：【{}】", message);
    }

    /**
     * (2) 使用Direct交换机发送消息测试
     * @param message
     */
    // 1.手动创建或者使用bean创建交换机和队列后，然后使用@RabbitListener注解监听队列。
//    @RabbitListener(queues = "direct.queue1")
//    public void listenDirectQueue1(String message) {
//        log.info("消费者1监听到 direct.queue1 的消息：【{}】", message);
//    }
//
//    @RabbitListener(queues = "direct.queue2")
//    public void listenDirectQueue2(String message) {
//        log.info("消费者2监听到 direct.queue2 的消息：【{}】", message);
//    }

    // 2.基于注解的方式，创建交换机和队列，并监听队列。不需要再额外手动或者使用@Bean配置类创建队列和交换机。
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1", durable = "true"),
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String message) {
        log.info("消费者1监听到 direct.queue1 的消息：【{}】", message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2", durable = "true"),
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String message) {
        log.info("消费者2监听到 direct.queue2 的消息：【{}】", message);
    }

    /**
     * (3) 使用 Topic交换机发送消息测试
     * @param message
     */
    @RabbitListener(queues = "topic.queue1")
    public void listenTopicQueue1(String message) {
        log.info("消费者1监听到 topic.queue1 的消息：【{}】", message);
    }

    @RabbitListener(queues = "topic.queue2")
    public void listenTopicQueue2(String message) {
        log.info("消费者2监听到 topic.queue2 的消息：【{}】", message);
    }

    /**
     * (4) 测试消息转换器
     *  消息处理过程：
     *      1. 发消息的时候SpringAMQP会将 map类型的消息转成字节发到mq。
     *      2. mq把消息投递给我们的时候是字节。
     *      3. 我们在监听消息的时候，SpringAMQP会把接收到的字节转成 map类型。
     */
    @RabbitListener(queues = "object.queue")
    public void listenObjectQueue(Map<String, Object> msg) {
        log.info("消费者监听到 object.queue 的消息：【{}】", msg); // 消费者监听到 object.queue 的消息：【{name=Jack, age=21}】
    }

    /**
     * 定义消费者，死信交换机测试，监听死信的队列，还需要定义一个正常的交换机和队列并绑定 NormalConfiguration（使用传统配置类的方式定义，因为会出现消费者，而我们的普通队列不需要消费者）然后将正常的队列与死信息交换机绑定。
     * 这段代码定义了一个RabbitMQ消费者，监听名为dlx.queue的死信队列。当接收到消息时，会将消息内容记录到日志中。
     * @RabbitListener注解配置了队列绑定信息，指定了队列、交换机和路由键。
     * listenDlxQueue方法接收消息并记录日志。
     *
     * 消息运转整体流程：
     *    1.需要延迟的消息发送到正常交换机 normalExchange，
     *    2.正常交换机到达正常队列 normalQueue，由于正常队列没有消费者，所以到达过期时间以后，消息会变成死信。
     *    3.死信消息会投递到死信交换机 dlx.direct。
     *    4.死信交换机到达死信队列 dlxQueue。
     *    5.到达消费者后，消费者进行消费。
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "dlx.queue", durable = "true"),
            exchange = @Exchange(name = "dlx.direct", type = ExchangeTypes.DIRECT),
            key = {"hi"}
    ))
    public void listenDlxQueue(String message) {
        log.info("消费者监听到 dlx.queue 的消息：【{}】", message);
    }

    /**
     * 定义消费者，测试使用延迟消息插件创建的延迟队列，这样只需创建一套正常的交换机和队列，无需再创建其他队列和交换机，也可以实现延迟消息。
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "delay.queue", durable = "true"),
            exchange = @Exchange(name = "delay.direct", delayed = "true"),
            key = {"hi"}
    ))
    public void listenDelayQueue(String message) {
        log.info("消费者监听到 delay.queue 的消息：【{}】", message);
    }
}
